﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using Algoritmia.DataStructures.MergeFindSets;
using System.Windows;

namespace Demos.MFSets
{
    public delegate void PruebaInsercionesHandler(double tiempoAcumulado, int numInserciones, int tipo);

    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private bool[] _selecciones;
        public bool[] Selecciones
        {
            get { return _selecciones; }
            set
            {
                _selecciones = value;
                NotifyPropertyChanged("Selecciones");
            }
        }

        private string[] _tiemposAcumulados;
        public string[] TiemposAcumulados
        {
            get { return _tiemposAcumulados; }
            set
            {
                _tiemposAcumulados = value;
                NotifyPropertyChanged("TiemposAcumulados");
            }
        }

        private string[] _tiemposMedios;
        public string[] TiemposMedios
        {
            get { return _tiemposMedios; }
            set
            {
                _tiemposMedios = value;
                NotifyPropertyChanged("TiemposMedios");
            }
        }

        private string _numOperaciones;
        public string NumOperaciones
        {
            get { return _numOperaciones; }
            set
            {
                _numOperaciones = value;
                NotifyPropertyChanged("NumOperaciones");
            }
        }

        private int _intervaloMediciones;
        public int IntervaloMediciones
        {
            get { return _intervaloMediciones; }
            set
            {
                _intervaloMediciones = value;
                NotifyPropertyChanged("IntervaloMediciones");
            }
        }

        public event PruebaInsercionesHandler PruebaInsercionesFinalizada;

        private MergeFindSet<int> _mfSetInt;
        private MergeFindSet<TestClass> _mfSetClass;
        private NaiveMergeFindSet<int> _naiveMfSetInt;
        private NaiveMergeFindSet<TestClass> _naiveMfSetClass;
        private PathCompressionMFSet<int> _pathCompressionMfSetInt;
        private PathCompressionMFSet<TestClass> _pathCompressionMfSetClass;
        private RankUnionMFSet<int> _rankUnionMfSetInt;
        private RankUnionMFSet<TestClass> _rankUnionMfSetClass;

        private Task pruebaMFSetStruct;
        private Task pruebaNaiveStruct;
        private Task pruebaPathStruct;
        private Task pruebaRankStruct;
        private Task pruebaMFSetClass;
        private Task pruebaNaiveClass;
        private Task pruebaPathClass;
        private Task pruebaRankClass;

        private List<KeyValuePair<int, double>>[] _resultados;

        public ICommand ComenzarCommand { get; private set; }
        public ICommand BorrarCommand { get; private set; }
        public ICommand SeleccionarTodoCommand { get; private set; }
        public ICommand VerGraficoCommand { get; private set; }

        public ViewModel()
        {
            Selecciones = new bool[7];
            TiemposAcumulados = new string[8];
            TiemposMedios = new string[8];
            NumOperaciones = "1000";
            IntervaloMediciones = 100;

            ComenzarCommand = new RelayCommand(Comenzar, PuedoComenzar);
            BorrarCommand = new RelayCommand(Borrar, () => TiemposMedios.Any(tiemposMedio => tiemposMedio != ""));
            SeleccionarTodoCommand = new RelayCommand(SeleccionarTodo, () => true);
            VerGraficoCommand = new RelayCommand(VerGrafico, () => _resultados.Any(res => res.Count > 0));

            PruebaInsercionesFinalizada += PruebaInsercionesPublicador;
            _resultados = new List<KeyValuePair<int, double>>[8];
            for (int i = 0; i < 8; i++)
            {
                _resultados[i] = new List<KeyValuePair<int, double>>();
            }
        }

        private void VerGrafico()
        {
            var grafico = new ChartView {Valores = _resultados};
            grafico.Show();
        }

        private bool PuedoComenzar()
        {
            if (Selecciones[4] || Selecciones[5])
            {
                for (var i = 0; i < 4; i++)
                {
                    if (Selecciones[i])
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private void SeleccionarTodo()
        {
            for (var i = 0; i < Selecciones.Count(); i++)
            {
                Selecciones[i] = true;
            }
            Selecciones[Selecciones.Count() - 1] = false;
            NotifyPropertyChanged("Selecciones");
        }

        private void Borrar()
        {
            TiemposAcumulados = new string[8];
            TiemposMedios = new string[8];
            for (int i = 0; i < 8; i++)
            {
                _resultados[i] = new List<KeyValuePair<int, double>>();
            }
        }

        private void Comenzar()
        {
            int numInserciones = 0;
            try
            {
                numInserciones = int.Parse(NumOperaciones);
            }
            catch (FormatException)
            {
                MessageBox.Show("Introduce un número de operaciones", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }

            numInserciones++;
            for (int i = 0; i < 8; i++)
            {
                _resultados[i] = new List<KeyValuePair<int, double>>();
            }
            var valores = new List<int>();
            var valoresClass = new List<TestClass>();

            //crear valores
            var r = new Random(DateTime.Now.Millisecond);
            for (int i = 0; i < numInserciones; i++)
            {
                valores.Add(r.Next());
                valoresClass.Add(new TestClass());
            }
            
            if (Selecciones[4])
            {
                if (Selecciones[0])
                {
                    EjecutarPrueba0(numInserciones, valores);
                }
                if (Selecciones[1])
                {
                    EjecutarPrueba1(numInserciones, valores);
                }
                if (Selecciones[2])
                {
                    EjecutarPrueba2(numInserciones, valores);
                }
                if (Selecciones[3])
                {
                    EjecutarPrueba3(numInserciones, valores);
                }
            }
            if (Selecciones[5])
            {
                if (Selecciones[0])
                {
                    EjecutarPrueba4(numInserciones, valoresClass);
                }
                if (Selecciones[1])
                {
                    EjecutarPrueba5(numInserciones, valoresClass);
                }
                if (Selecciones[2])
                {
                    EjecutarPrueba6(numInserciones, valoresClass);
                }
                if (Selecciones[3])
                {
                    EjecutarPrueba7(numInserciones, valoresClass);
                }
            }
        }

        private void EjecutarPrueba0(int numInserciones, IList<int> valores)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _mfSetInt = new MergeFindSet<int>();
            pruebaMFSetStruct = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _mfSetInt.Add(valores[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[0].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 0)));
            });
            pruebaMFSetStruct.Start();
        }

        private void EjecutarPrueba1(int numInserciones, IList<int> valores)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _naiveMfSetInt = new NaiveMergeFindSet<int>();
            pruebaNaiveStruct = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _naiveMfSetInt.Add(valores[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[1].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 1)));
            });
            pruebaNaiveStruct.Start();
        }

        private void EjecutarPrueba2(int numInserciones, IList<int> valores)
        {
            if (valores == null) throw new ArgumentNullException("valores");
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _pathCompressionMfSetInt = new PathCompressionMFSet<int>();
            pruebaPathStruct = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _pathCompressionMfSetInt.Add(valores[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[2].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 2)));
            });
            pruebaPathStruct.Start();
        }

        private void EjecutarPrueba3(int numInserciones, IList<int> valores)
        {
            if (valores == null) throw new ArgumentNullException("valores");
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _rankUnionMfSetInt = new RankUnionMFSet<int>();
            pruebaRankStruct = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _rankUnionMfSetInt.Add(valores[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[3].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 3)));
            });
            pruebaRankStruct.Start();
        }

        private void EjecutarPrueba4(int numInserciones, IList<TestClass> valoresClass)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _mfSetClass = new MergeFindSet<TestClass>();
            pruebaMFSetClass = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _mfSetClass.Add(valoresClass[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[4].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 4)));
            });
            pruebaMFSetClass.Start();
        }

        private void EjecutarPrueba5(int numInserciones, IList<TestClass> valoresClass)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _naiveMfSetClass = new NaiveMergeFindSet<TestClass>();
            pruebaNaiveClass = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _naiveMfSetClass.Add(valoresClass[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[5].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 5)));
            });
            pruebaNaiveClass.Start();
        }

        private void EjecutarPrueba6(int numInserciones, IList<TestClass> valoresClass)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _pathCompressionMfSetClass = new PathCompressionMFSet<TestClass>();
            pruebaPathClass = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _pathCompressionMfSetClass.Add(valoresClass[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[6].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 6)));
            });
            pruebaPathClass.Start();
        }

        private void EjecutarPrueba7(int numInserciones, IList<TestClass> valoresClass)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            _rankUnionMfSetClass = new RankUnionMFSet<TestClass>();
            pruebaRankClass = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _rankUnionMfSetClass.Add(valoresClass[i]);
                    if (i % IntervaloMediciones == 0)
                    {
                        _resultados[7].Add(new KeyValuePair<int, double>(i, (DateTime.Now - tiempo1).TotalMilliseconds));
                    }
                }
                DateTime tiempo2 = DateTime.Now;

                double tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, numInserciones, 7)));
            });
            pruebaRankClass.Start();
        }

        void PruebaInsercionesPublicador(double tiempoAcumulado, int numInserciones, int tipo)
        {
            TiemposAcumulados[tipo] = string.Format("Tiempo total acumulado: {0:#0.000} ms", tiempoAcumulado);
            TiemposMedios[tipo] = string.Format("Tiempo medio por operación: {0:#0.000} ms", tiempoAcumulado / numInserciones);
            NotifyPropertyChanged("TiemposAcumulados");
            NotifyPropertyChanged("TiemposMedios");
        }
    }
}
